package rtlmmap

/*
#cgo LDFLAGS: -lrtlsdr -lftcommap -L../rr/build
#include <unistd.h>
#include <rtl-sdr.h>
void *init_rtldriver(int n);
void* open_rtldriver(void *h);
int set_frequency_rtldriver(void *h,uint32_t fc);
int set_sample_rate_rtldriver(void *h,uint32_t sr);
int gain_set_rtldriver(void *h,int g);
int ppm_set_rtldriver(void *h,int g);
int start_rtldriver(void *h);
int suspend_rtldriver(void *h);
int resume_rtldriver(void *h);
int get_device_count();
int get_usb_strings(int index,char *manufact, char *product, char *serial);
*/
import "C"
import (
	"bytes"
	"errors"
	"fmt"
	"unsafe"

	"github.com/prometheus/common/log"
)

// These constants are used to set default parameter values.
const (
	DefaultGAIN           = "auto"
	DefaultFc             = 80e6
	DefaultRs             = 1.024e6
	DefaultReadSize       = 1024
	CrystalFreq           = 28800000
	DefaultSampleRate     = 2048000
	DefaultAsyncBufNumber = 32
	DefaultBufLength      = (16 * 16384)
	MinimalBufLength      = 512
	MaximalBufLength      = (256 * 16384)
	LIBUSB_ERROR_OTHER    = -99
)

// Note, librtlsdr's SetFreqCorrection returns an error value of
// -2 when the current ppm is the same as the requested ppm, but
// gortlsdr replaces the -2 with nil. Also, most of librtlsdr's
// functions return 0 on success and -1 when dev is invalid but
// some return 0 when dev is invalid, go figure.
const (
	libSuccess = iota * -1
	libErrorIo
	libErrorInvalidParam
	libErrorAccess
	libErrorNoDevice
	libErrorNotFound
	libErrorBusy
	libErrorTimeout
	libErrorOverflow
	libErrorPipe
	libErrorInterrupted
	libErrorNoMem
	libErrorNotSupported
	libErrorOther = LIBUSB_ERROR_OTHER
)

var libErrMap = map[int]error{
	libSuccess:           nil,
	libErrorIo:           errors.New("input/output error"),
	libErrorInvalidParam: errors.New("invalid parameter(s)"),
	libErrorAccess:       errors.New("access denied (insufficient permissions)"),
	libErrorNoDevice:     errors.New("no such device (it may have been disconnected)"),
	libErrorNotFound:     errors.New("entity not found"),
	libErrorBusy:         errors.New("resource busy"),
	libErrorTimeout:      errors.New("operation timed out"),
	libErrorOverflow:     errors.New("overflow"),
	libErrorPipe:         errors.New("pipe error"),
	libErrorInterrupted:  errors.New("system call interrupted (perhaps due to signal)"),
	libErrorNoMem:        errors.New("insufficient memory"),
	libErrorNotSupported: errors.New("operation not supported or unimplemented on this platform"),
	libErrorOther:        errors.New("unknown error"),
}

func libError(errno int) error {
	if err, ok := libErrMap[errno]; ok {
		return err
	}
	return errors.New("unknown error")
}

type Device struct {
	handle unsafe.Pointer
	device unsafe.Pointer
}

func GetDeviceCount() int {
	c := C.get_device_count()
	fmt.Println("Find Device: ", c)
	return int(c)
}

func GetDeviceString(index int) (manufact, product, serial string, err error) {
	m := make([]byte, 257) // includes space for NULL byte
	p := make([]byte, 257)
	s := make([]byte, 257)
	i := int(C.get_usb_strings(C.int(index),
		(*C.char)(unsafe.Pointer(&m[0])),
		(*C.char)(unsafe.Pointer(&p[0])),
		(*C.char)(unsafe.Pointer(&s[0]))))
	return string(bytes.Trim(m, "\x00")), string(bytes.Trim(p, "\x00")),
		string(bytes.Trim(s, "\x00")), libError(i)
}

func NewDevice(n int) *Device {
	return &Device{C.init_rtldriver(C.int(n)), unsafe.Pointer(nil)}
}

func (d *Device) Open() {
	(*d).device = unsafe.Pointer(C.open_rtldriver(d.handle))

}

func (d *Device) Start() {
	C.start_rtldriver(d.handle)
}

func (d *Device) Suspend() {
	C.suspend_rtldriver(d.handle)
}

func (d *Device) Resume() {
	C.resume_rtldriver(d.handle)
}

func (d *Device) SetFrequency(fc int) {
	ufc := uint(fc)
	C.set_frequency_rtldriver(d.handle, C.uint32_t(ufc))
	if hwinfo, err := d.GetHwInfo(); err == nil {
		log.Info(hwinfo)
		if hwinfo.PPM != 0 {
			ppm := -int(hwinfo.PPM)
			if ppm < 120 && ppm > -120 {
				log.Info("set ppm to ", ppm)
				d.SetPPM(ppm)
				return
			}
		}
	}

}

func (d *Device) SetSampleRate(sr int) {
	usr := uint(sr)
	C.set_sample_rate_rtldriver(d.handle, C.uint32_t(usr))

}
func (d *Device) SetGain(g int) {
	C.gain_set_rtldriver(d.handle, C.int(g))
}

func (d *Device) SetPPM(p int) {
	C.ppm_set_rtldriver(d.handle, C.int(p))
}

// HwInfo holds dongle specific information.
type HwInfo struct {
	VendorID     uint16
	ProductID    uint16
	Manufact     string
	Product      string
	Serial       string
	HaveSerial   bool
	EnableIR     bool
	RemoteWakeup bool
	PPM          int8
}

const (
	// EepromSize is the char size of the EEPROM
	EepromSize = 256
	// MaxStrSize = (max string length - 2 (header bytes)) \ 2,
	// where each info char is followed by a null char.
	MaxStrSize = 35
	// StrOffsetStart is the string descriptor offset start
	StrOffsetStart = 0x09
	// PPMPos is PPM Pos
	PPMPos = StrOffsetStart + ((MaxStrSize*2)+2)*3
)

// WriteEeprom writes data to the EEPROM.
func (d *Device) WriteEeprom(data []uint8, offset uint8, leng uint16) (err error) {
	i := int(C.rtlsdr_write_eeprom((*C.rtlsdr_dev_t)(d.device),
		(*C.uint8_t)(unsafe.Pointer(&data[0])),
		C.uint8_t(offset),
		C.uint16_t(leng)))
	switch {
	default:
		err = nil
	case i == -1:
		err = errors.New("device handle is invalid")
	case i == -2:
		err = errors.New("EEPROM size exceeded")
	case i == -3:
		err = errors.New("no EEPROM was found")
	case i < -4:
		err = errors.New("unknown error")
	}
	return
}

// ReadEeprom returns data read from the EEPROM.
func (d *Device) ReadEeprom(data []uint8, offset uint8, leng uint16) (err error) {
	i := int(C.rtlsdr_read_eeprom((*C.rtlsdr_dev_t)(d.device),
		(*C.uint8_t)(unsafe.Pointer(&data[0])),
		C.uint8_t(offset),
		C.uint16_t(leng)))
	switch {
	default:
		err = nil
	case i == -1:
		err = errors.New("device handle is invalid")
	case i == -2:
		err = errors.New("EEPROM size exceeded")
	case i == -3:
		err = errors.New("no EEPROM was found")
	case i < -4:
		err = errors.New("unknown error")
	}
	return
}

// GetHwInfo gets the dongle's information items.
func (d *Device) GetHwInfo() (info HwInfo, err error) {
	data := make([]uint8, EepromSize)
	if err = d.ReadEeprom(data, 0, EepromSize); err != nil {
		return
	}
	if (data[0] != 0x28) || (data[1] != 0x32) {
		err = errors.New("no valid RTL2832 EEPROM header")
		return
	}
	info.VendorID = (uint16(data[3]) << 8) | uint16(data[2])
	info.ProductID = (uint16(data[5]) << 8) | uint16(data[4])
	if data[6] == 0xA5 {
		info.HaveSerial = true
	}
	if t := data[7] & 0x01; t == 1 {
		info.RemoteWakeup = true
	}
	if t := data[7] & 0x02; t == 2 {
		info.EnableIR = true
	}
	info.PPM = int8(data[PPMPos])
	info.Manufact, info.Product, info.Serial, err = GetStringDescriptors(data)
	return
}

// GetStringDescriptors gets the manufacturer, product, and serial
// strings from the hardware's eeprom.
func GetStringDescriptors(data []uint8) (manufact, product, serial string, err error) {
	pos := StrOffsetStart
	for _, v := range []*string{&manufact, &product, &serial} {
		l := int(data[pos])
		if l > (MaxStrSize*2)+2 {
			err = errors.New("string value too long")
			return
		}
		if data[pos+1] != 0x03 {
			err = errors.New("string descriptor invalid")
			return
		}
		j := 0
		k := 0
		m := make([]uint8, l-2)
		for j = 2; j < l; j += 2 {
			m[k] = data[pos+j]
			k++
		}
		*v = string(bytes.Trim(m, "\x00"))
		pos += j
	}
	return
}

// SetStringDescriptors sets the manufacturer, product, and serial
// strings on the hardware's eeprom.
func SetStringDescriptors(info HwInfo, data []uint8) (err error) {
	e := ""
	if len(info.Manufact) > MaxStrSize {
		e += "Manufact:"
	}
	if len(info.Product) > MaxStrSize {
		e += "Product:"
	}
	if len(info.Serial) > MaxStrSize {
		e += "Serial:"
	}
	if len(e) != 0 {
		err = errors.New(e + " string/s too long")
		return
	}
	pos := StrOffsetStart
	for _, v := range []string{info.Manufact, info.Product, info.Serial} {
		data[pos] = uint8((len(v) * 2) + 2)
		data[pos+1] = 0x03
		i := 0
		j := 0
		for i = 2; i <= len(v)*2; i += 2 {
			data[pos+i] = v[j]
			data[pos+i+1] = 0x00
			j++
		}
		pos += i
	}
	return
}

func SetHwInfoPPM(info HwInfo, data []uint8) (err error) {
	pos := PPMPos
	data[pos] = uint8(info.PPM)
	return
}

func (d *Device) Dump() {
	hwinfo, err := d.GetHwInfo()
	if err == nil {
		fmt.Println(hwinfo)
	} else {
		fmt.Println(err.Error())
	}
}
